%{
#include <iostream>
#include <stdio.h>
#include <map>
#include <deque>
#include "declarations.h"
#define print(id) cout << id << endl;
using namespace std;
extern int yylex();
extern FILE *yyin;
void yyerror(const char* s) { printf("Error of parser\n");}

symbol_table table;
struct values_struct temp2;
%}

%union
{	
	struct values_struct values;
}

%token VOID INT DOUBLE BOOL STRING INULL
%token WHILE FOR IF ELSE RETURN BREAK PRINT READINT READLINE
%token TRUE FALSE
%token LESSEQTHAN MOREEQTHAN  EQUALCOMP DIFFCOMP
%token AND OR
%token CHEX CDEC CSTRING CDOUBLE
%token<values> ID

%type<values> constant variable type

%left '='
%left OR
%left AND
%left EQUALCOMP DIFFCOMP
%left '<' LESSEQTHAN '>' MOREEQTHAN
%left '+' '-'
%left '*' '/' '%'
%left '!'
%left '['

%nonassoc LOWER_THAN_ELSE
%nonassoc ELSE

%start program


%%

program:	decl
        | 	program  decl
        ;

decl:       variableDecl
        | 	functionDecl  { pushScope(table); }
        ;

variableDecl:	variable ';'
        ;

variable: 	type ID
			{ 
				if(!findKey(table, string($2.name))) 
				{
					$$.type = $1.type;
					cout << "Variable added:  " << $2.name << endl;
					addSymbol(table, string($2.name), $2);
				}
				else
				{
					$$.type = error_type;
					cout << "Variable already exists:  " << $2.name << endl;
					return 0;
				}					
			}
        |   type ID '[' CDEC ']'
			{ 
				if(!findKey(table, $2.name)) 
				{
					$$.type = $1.type;
					addSymbol(table, $2.name, $2);
				}
				else
				{
					$$.type = error_type;
					cout << "Array already exists: " << $2.name << endl;
					return 0;
				}					
			}
        ;

type:        INT 	{ $$.type = int_type; }
        |    DOUBLE { $$.type = double_type; }
        |    BOOL 	{ $$.type = bool_type; }
        |    STRING	{ $$.type = string_type; }
        ;

functionDecl:   type ID '(' formals ')'  instBlockList
        |    	VOID ID '(' formals ')' instBlockList
		|    	type ID '(' ')'  instBlockList
        |    	VOID ID '(' ')' instBlockList       
        ;

formals:    variable
        | 	formals ',' variable     
        ;

instBlockList: 	startScope instBlock1 instBlock2 endScope 
        | 		startScope endScope
        | 		startScope instBlock1 endScope
        | 		startScope instBlock2 endScope
        ;

startScope: 	'{' { pushScope(table); cout << "new scope\n";}
        ;
        
endScope:		'}' { popScope(table); cout << "end scope\n";}
		;

instBlock1:    	variableDecl
        | 		instBlock1 variableDecl
        ;

instBlock2:    	inst 
        | 		instBlock2 inst
        ;

inst:       expr ';'
        | 	';'
        |   instIf
        |   instWhile
        |   instFor
        |   instBreak
        |   instReturn
        |   instPrint
        |   instBlockList
        ;

instIf:     IF '(' expr ')' inst%prec LOWER_THAN_ELSE
        | 	IF '(' expr ')' inst ELSE inst           
        ;

instWhile:  WHILE '(' expr ')' inst
        ;

instFor:    FOR '(' expr';' expr';' expr')' inst
        | 	FOR '(' ';' expr';'expr')' inst
        | 	FOR '(' expr';' expr';' ')' inst
        | 	FOR '(' ';' expr';' ')' inst
        ;

instBreak:   	RETURN expr ';'
        | 		RETURN ';'
        ;

instReturn:    	BREAK ';'
        ;

instPrint:    	PRINT '(' list_instPrint ')' ';'
        ;

list_instPrint:     expr
        | 			list_instPrint ',' expr
        ;
   
expr:        valueL '=' expr
        |    constant
        |    valueL
        |    call
        |    '(' expr ')'
        |    expr '+' expr
        |    expr '-' expr
        |    expr '*' expr
        |    expr '/' expr
        |    expr '%' expr
        |    '-' expr
        |    expr '<' expr
        |    expr LESSEQTHAN expr
        |    expr '>' expr
        |    expr MOREEQTHAN expr       
        |    expr EQUALCOMP expr
        |    expr DIFFCOMP expr   
        |    expr AND expr 
        |    expr OR expr
        |    '!' expr
        |    READINT '(' ')'
        |    READLINE '(' ')'
        ;

valueL:     ID
        | 	expr '[' expr ']'
        ;

call:       ID '(' reals ')'
        | 	ID '('  ')'
        ;

reals:      expr
        | 	reals ',' expr
        ;

constant:   CDEC	{ $$.type = decimal_type }
		|	CHEX	{ $$.type = hex_type }
        | 	CDOUBLE	{ $$.type = double_type; }
        | 	TRUE 	{ $$.type = bool_type; }
        | 	FALSE	{ $$.type = bool_type; }
        | 	CSTRING { $$.type = string_type; }
        | 	INULL	{ $$.type = pointer_type; }
        ;

%%

main(int argc, char **argv)
{
    if(argc != 2)
    {
        cout << "Bad arguments" << endl;
        return 1;
    }

    FILE *file = fopen(argv[1], "r");

    if(!file)
    {
        cout << "Invalid or wrong file" << endl;
        return 1;
    }

    yyin = file;	
    
    // push global scope
    pushScope(table);

    do
    {
        yyparse();
    }
    while(!feof(yyin));

    return 0;
}
